package cn.lovexiaov.javafx;

import cn.lovexiaov.javafx.model.Person;
import cn.lovexiaov.javafx.model.PersonListWrapper;
import cn.lovexiaov.javafx.view.PersonEditDialogController;
import cn.lovexiaov.javafx.view.PersonOverviewController;
import cn.lovexiaov.javafx.view.RootLayoutController;
import java.io.File;
import java.io.IOException;

import java.util.prefs.Preferences;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class MainApp extends Application {

  private Stage primaryStage;
  private BorderPane rootLayout;

  public ObservableList<Person> getPersonData() {
    return personData;
  }

  private ObservableList<Person> personData = FXCollections.observableArrayList();

  public MainApp() {
    personData.add(new Person("Hans", "Muster"));
    personData.add(new Person("Ruth", "Mueller"));
    personData.add(new Person("Heinz", "Kurz"));
    personData.add(new Person("Cornelia", "Meier"));
    personData.add(new Person("Werner", "Meyer"));
    personData.add(new Person("Lydia", "Kunz"));
    personData.add(new Person("Anna", "Best"));
    personData.add(new Person("Stefan", "Meier"));
    personData.add(new Person("Martin", "Mueller"));
  }

  @Override
  public void start(Stage primaryStage) {
    this.primaryStage = primaryStage;
    this.primaryStage.setTitle("AddressApp");

    // Set the application icon.
    this.primaryStage.getIcons().add(new Image("image/address_book.png"));

    initRootLayout();

    showPersonOverview();
  }

  /**
   * Initializes the root layout and tries to load the last opened
   * person file.
   */
  public void initRootLayout() {
    try {
      // Load root layout from fxml file.
      FXMLLoader loader = new FXMLLoader();
      loader.setLocation(MainApp.class.getClassLoader()
          .getResource("fxml/RootLayout.fxml"));
      rootLayout = (BorderPane) loader.load();

      // Show the scene containing the root layout.
      Scene scene = new Scene(rootLayout);
      primaryStage.setScene(scene);

      // Give the controller access to the main app.
      RootLayoutController controller = loader.getController();
      controller.setMainApp(this);

      primaryStage.show();
    } catch (IOException e) {
      e.printStackTrace();
    }

    // Try to load last opened person file.
    File file = getPersonFilePath();
    if (file != null) {
      loadPersonDataFromFile(file);
    }
  }

  /**
   * Shows the person overview inside the root layout.
   */
  public void showPersonOverview() {
    try {
      // Load person overview.
      FXMLLoader loader = new FXMLLoader();
      loader.setLocation(MainApp.class.getClassLoader().getResource("fxml/PersonOverview.fxml"));
      AnchorPane personOverview = (AnchorPane) loader.load();

      // Set person overview into the center of root layout.
      rootLayout.setCenter(personOverview);

      // Give the controller access to the main app.
      PersonOverviewController controller = loader.getController();
      controller.setMainApp(this);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * Opens a dialog to edit details for the specified person. If the user
   * clicks OK, the changes are saved into the provided person object and true
   * is returned.
   *
   * @param person the person object to be edited
   * @return true if the user clicked OK, false otherwise.
   */
  public boolean showPersonEditDialog(Person person) {
    try {
      // Load the fxml file and create a new stage for the popup dialog.
      FXMLLoader loader =
          new FXMLLoader(MainApp.class.getClassLoader().getResource("fxml/PersonEditDialog.fxml"));
      AnchorPane page = loader.load();

      // Create the dialog stage.
      Stage dialogStage = new Stage();
      dialogStage.setTitle("Edit Person");
      dialogStage.initModality(Modality.WINDOW_MODAL);
      dialogStage.initOwner(primaryStage);
      Scene scene = new Scene(page);
      dialogStage.setScene(scene);

      // Set the person into the controller.
      PersonEditDialogController controller = loader.getController();
      controller.setDialogStage(dialogStage);
      controller.setPerson(person);

      // Show the dialog and wait until the user close it.
      dialogStage.showAndWait();

      return controller.isOkClicked();
    } catch (IOException e) {
      e.printStackTrace();
      return false;
    }
  }

  /**
   * Returns the main stage.
   */
  public Stage getPrimaryStage() {
    return primaryStage;
  }

  public static void main(String[] args) {
    launch(args);
  }

  /**
   * Returns the person file preference, i.e. the file that was last opened.
   * The preference is read from the OS specific registry. If no such
   * preference can be found, null is returned.
   */
  public File getPersonFilePath() {
    Preferences prefs = Preferences.userNodeForPackage(MainApp.class);
    String filePath = prefs.get("filePath", null);
    if (filePath != null) {
      return new File(filePath);
    } else {
      return null;
    }
  }

  /**
   * Sets the file path of the currently loaded file. The path is persisted in
   * the OS specific registry.
   *
   * @param file the file or null to remove the path
   */
  public void setPersonFilePath(File file) {
    Preferences prefs = Preferences.userNodeForPackage(MainApp.class);
    if (file != null) {
      prefs.put("filePath", file.getPath());

      // Update the stage title.
      primaryStage.setTitle("AddressApp - " + file.getName());
    } else {
      prefs.remove("filePath");

      // Update the stage title.
      primaryStage.setTitle("AddressApp");
    }
  }

  /**
   * Loads person data from the specified file. The current person data will
   * be replaced.
   */
  public void loadPersonDataFromFile(File file) {
    try {
      JAXBContext context = JAXBContext
          .newInstance(PersonListWrapper.class);
      Unmarshaller um = context.createUnmarshaller();

      // Reading XML from the file and unmarshalling.
      PersonListWrapper wrapper = (PersonListWrapper) um.unmarshal(file);

      personData.clear();
      personData.addAll(wrapper.getPersons());

      // Save the file path to the registry.
      setPersonFilePath(file);
    } catch (Exception e) { // catches ANY exception
      Alert alert = new Alert(Alert.AlertType.ERROR);
      alert.setTitle("Error");
      alert.setHeaderText("Could not load data");
      alert.setContentText("Could not load data from file:\n" + file.getPath());

      alert.showAndWait();
    }
  }

  /**
   * Saves the current person data to the specified file.
   */
  public void savePersonDataToFile(File file) {
    try {
      JAXBContext context = JAXBContext
          .newInstance(PersonListWrapper.class);
      Marshaller m = context.createMarshaller();
      m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

      // Wrapping our person data.
      PersonListWrapper wrapper = new PersonListWrapper();
      wrapper.setPersons(personData);

      // Marshalling and saving XML to the file.
      m.marshal(wrapper, file);

      // Save the file path to the registry.
      setPersonFilePath(file);
    } catch (Exception e) { // catches ANY exception
      Alert alert = new Alert(Alert.AlertType.ERROR);
      alert.setTitle("Error");
      alert.setHeaderText("Could not save data");
      alert.setContentText("Could not save data to file:\n" + file.getPath());

      alert.showAndWait();
    }
  }
}